<!--
20240814最后修改
@author ljy
对话框公共组件，用于统一对话框的样式。本主键不对对话框的主体部分进行处理
一、使用方法
1.引入组件
import BaseDialog from '@/components/base/dialog/BaseDialog.vue';
2.调用组件
<BaseDialog ref="baseDialogRef" :dialogWidth="title" @closed="closed">
3.定义该组件对象
//定义对话框组件对象
const baseDialogRef = ref();
4.打开和关闭对话框的方法
baseDialogRef.value.openDialog();
baseDialogRef.value.closeDialog();
5.对话框默认提交事件回调
说明：表单提交事件（组件不进行任何提交的实际处理，需要父组件完成）
 （1）基本提交
首先在标签中加入事件 @dialogSubmit="dialogSubmit()"
<BaseDialog ref="baseDialogRef" v-bind="$props" @dialogSubmit="dialogSubmit()">
然后在setup中调用即可
 const dialogSubmit = () => {
  //表单提交代码
}
（1）对话框高级提交事件-例如对话框中包含表单，那就要将表单对象传入
首先在标签中加入事件@dialogSubmit="dialogSubmit(formRef)"
<BaseDialog ref="baseDialogRef" v-bind="$props" @dialogSubmit="dialogSubmit(formRef)">
然后在setup中调用即可
const formRef = ref<FormInstance>(); // 定义表单的ref对象
//执行回调函数
const dialogSubmit = async (formEl: FormInstance | undefined) => {
  console.log(111);
  // 提交表单
  if (!formEl) return;
  // 表单验证
  await formEl.validate(async (valid, fields) => {
    // 如果通过表单验证
    if (valid) {
    }
  });
};
6.对话框关闭事件回调
说明：表单提交事件（组件不进行任何提交的实际处理，需要父组件完成）
 （1）基本提交
 首先在标签中加入事件 @dialogClosed="closed"
  <BaseDialog ref="baseDialogRef" v-bind="$props" :dialogTitle="titleResult" @closed="closed" @dialogSubmit="dialogSubmit(formRef)">
 然后在setup中调用
const closed = () => {
  // 重置表单
  formRef.value!.resetFields();
  // Object.assign(formObject.value, formInitObject);
  // 关闭对话框后还要处理的信息继续传递父组件
  emit('afterCloseDialog');
};
  -->
<template>
  <div class="dialog">
    <!-- 如果要先全属性props传递，再给特殊的值做传递，要按照下面这个代码顺序（v-bind="$props" :dialogTitle="titleResult"）传递 -->
    <BaseDialog ref="baseDialogRef" v-bind="$props" :dialogTitle="dialogTitle" @dialogClosed="dialogClosed" @dialogSubmit="dialogSubmit">
      <template #dialog_body>
        <FormComponent
          ref="formRef"
          v-bind="$props"
          v-model="form"
          v-model:formTitle="dialogTitle"
          @beforeFormSubmit="emit('beforeFormSubmit', $event)"
          @afterFormSubmitSuccess="afterFormSubmitSuccess"
          @afterFormSubmitFailure="emit('afterFormSubmitFailure')"
          @afterFormReset="afterFormReset"
        >
          <!--测试用，经测试有效 
          <template #password>
            <slot name="password"> </slot>
          </template> -->
          <!-- 遍历表单的动态插槽 -->
          <template #[item.prop] v-for="(item, index) in formHeader" :key="index">
            <slot
              v-if="item.type === 'custom' || item.type === 'customTemplate'"
              :name="'form_' + item.prop"
              v-bind:item="item"
              v-bind:formItem="form[item.prop]"
            >
            </slot>
          </template>
          <!--表单插槽底部，用于补充其他的插槽-->
          <template #form_bottom>
            <slot name="form_bottom"></slot>
          </template>
        </FormComponent>
      </template>
      <template #dialog_foot>
        <slot name="dialog_foot"></slot>
      </template>
      <template #dialog_foot_left>
        <slot name="dialog_foot_left"></slot>
      </template>
      <template #dialog_foot_right>
        <slot name="dialog_foot_right"></slot>
      </template>
    </BaseDialog>
  </div>
</template>

<script lang="ts" setup>
/**
 * 特别说明
 * 1.本组件不开发表单的重置功能
 * （1）设计思路：由于本组件有特殊表单元素的插槽，因此表单对象是从父组件传入，按照vue的设计思维，重置应该在父组件完成
 * （2）技术上无法实现完美的表单重置功能。
 * 因为对话框组件重置时记录的是第一次对话框打开时的初始值，因此在先执行编辑再执行添加会出现问题。现象是添加成功并重置表单后，表单数据变为第一次打开时的数据。
 * 例如第一次打开是账户user的编辑信息，那么无论后续操作了多少次对话框，执行formRef.value!.resetFields();清空表单后，都会重置为账户user的编辑信息。
 * 因为在props无法修改form的信息，因此建议在父组件去操作手动清空数据。
 * 2.功能扩展
 * （1）因为element plus从2.5版本后下拉框的宽度均要自己设置，为了保证下拉框布局问题，特意针对每个类似下拉框的组件增加宽度属性，但这个属性将不对其他文本框生效（因为其他文本框可以自动获取宽度）
 *
 */
import BaseDialog from '@/components/base/dialog/BaseDialogComponent.vue';
import FormComponent from '@/components/base/table/form/FormComponent.vue';
import { ref, computed, inject, reactive, nextTick } from 'vue';
import type { FormHeader, ResponseData } from '@/types/interface/base_interface';
import type { FormInstance, FormRules } from 'element-plus';
import { defineEmits } from 'vue';
import Request from '@/api/request/request';
const emit = defineEmits([
  'dialogClosed',
  'beforeSubmit',
  'afterSubmit',
  'afterSubmitSuccess',
  'afterFormSubmitFailure',
  'afterReset',
  'beforeFormSubmit'
]);

// 接收父组件的值
interface Props {
  // form属性在20220430修改为可选属性，意图避免在父组件初始化时，避免DIALOG初始化该属性报UNDEFINED
  // 虽然在父组件使用诸如const form = ref<Department>({});可以解决此问题，但是在typescript的Department接口中，如果包含了必须的属性，则该表单必须加上必须属性的值
  // 例如：const form = ref<Department>({ id: 1, name: '大数据技术'})，导致父组件应用中缺乏灵活性
  // form?: Record<string, any>; // 表单对象。
  //下面是基础对话框组件BaseDialog的基础属性（直接复制）
  dialogWidth?: string; // 对话框宽度
  // dialogTitle?: string; // 对话框标题,前台传入，不用加入添加或编辑的文本。添加或编辑的文本由action属性决定
  showDialogCancelButton?: Boolean; // 是否显示取消按钮
  dialogCancelButtonText?: string; // 取消按钮上的文字
  showDialogConfirmButton?: Boolean; //是否显示确认按钮
  dialogConfirmButtonText?: string; // 确认按钮上的文字
  showDialogFoot?: Boolean; // 是否显示对话框脚部
  dialogBodyStyle?: any; // 对话框主体内容的样式，要求以对象方式传入
  //下面是基础表单组件FormComponent的基础属性
  formHeader: FormHeader[]; // 表单元素集合,用于生成表单
  formInline?: Boolean; // 表单是否未行内元素，对应form的inline属性。默认不是。注意如果是行内元素，建议在每个FormHeader中增加width描述
  formAction?: string; // 表单的处理类型，主要有post、put、patch等，分别代表插入、编辑和局部编辑
  formRules?: FormRules; // 整个表单的校检规则
  autoTitle?: Boolean; //是否自动标题。即根据props中的formAction属性来判断标题是否增加新增、修改等。
  formLabelWidth?: string; // 表单宽度，对应el-form的label-width属性
  formLabelPosition?: string; // 表单的标签对齐方式，对应el-form的label-position属性（left、right、top）。
  formUrl: string; // 表单提交到服务器的地址，要求在服务器的接口地址一样，只是方式不同。
  showFormFoot?: Boolean; //是否显示表单底部，即两个默认的按钮。
  showFormResetButton?: Boolean; // 是否显示重置按钮
  clearFormValueAfterSubmitSuccess?: Boolean; //表单提交成功后是否清空值，ture表示清空，默认不清空。
  dataPickerSize?: 'default' | 'large' | 'small'; //日期输入框尺寸
}

const props = withDefaults(defineProps<Props>(), {
  formAction: 'post'
  // form: () => ({}) // 返回空对象
});

//接收表单对象
const form: Record<string, any> = defineModel({
  required: true
});
// 对话框标题,前台传入，不用加入添加或编辑的文本。添加或编辑的文本由action属性决定
const dialogTitle = defineModel('dialogTitle', {
  required: false,
  default: '表单'
});
// inject{
//   loadTableList:{value:loadTableList,default:null}
// }
// 接收父容器传递过来的表格刷新函数，用于刷新表格信息
//注意，使用这种方式控制台不会警告，但是下面注释掉的方式会警告
//如果传递的是参数可以用下面写法
// const loadTableList = inject('loadTableList', null);
//如果传递的是函数可以用下面写法
const loadTableList = inject<() => void>('loadTableList', () => {});
//注意，使用这种方式控制台不会警告，但是下面注释掉的方式会警告
// if (typeof inject<() => void>('loadTableList') !== 'undefined') {
//   loadTableList = inject<() => void>('loadTableList'); // 表格函数
// }

//定义对话框组件对象
const baseDialogRef = ref();
// 表单初始值
// const formInitObject = computed(JSON.parse(JSON.stringify(unref(formObject)))); // 用于表单恢复初始值
// 对话框标题，根据action生成标题和相应的操作提示
const formRef = ref(); // 定义表单的ref对象
// 打开此对话框的函数
const openDialog = async () => {
  await nextTick();
  // 第一次打开对话框时会因为form没渲染导致formRef无法获取到dom对象，因此先进行判断。如果是第一次打开时则不进行表单重置，也没必要
  if (formRef.value !== undefined) {
    // 重点也是把上一次的表单验证状态去掉
    formRef.value.formReset();
  }
  baseDialogRef.value.openDialog();
};
const closeDialog = () => {
  baseDialogRef.value.closeDialog();
};
// 关闭对话框时候
const dialogClosed = () => {
  // 重置表单
  // formRef.value.formReset();
  // Object.assign(formObject.value, formInitObject);
  // 关闭对话框后还要处理的信息交给父组件
  emit('dialogClosed');
};
// 提交表单函数,包含添加和编辑操作
// 如果表单为disabled则不提交
const dialogSubmit = async () => {
  // 提交表单前要处理的钩子函数，如果要在提交服务端前处理一些数据，可以在这里处理，例如处理关联对象的值，将departmentId处理为department.id再传递
  emit('beforeSubmit');
  //验证并提交表单
  formRef.value.formSubmit(formRef.value.formRef);
};

const afterFormSubmitSuccess = () => {
  //表单成交提供后的回调
  emit('afterSubmitSuccess');
  //如果是编辑则关闭对话框
  if (props.formAction === 'put' || props.formAction === 'patch') {
    baseDialogRef.value.closeDialog();
  }
};
const afterFormReset = async () => {
  //重置后交给父组件处理
  emit('afterReset');
};
/**
 * 清除表单指定字段的验证
 * 设计与20250419，主要针对表单元素是自定义组件的情况。
 * 例如自定义组件中也有表单，当表单验证失败时，这个子表单里面的验证元素也会全部失效
 */
const clearFieldValidation = (fieldName: string) => {
  //移除表单项的校验结果。
  formRef.value!.clearFieldValidation(fieldName);
};
/**
 * 验证表单指定字段
 * 设计与20250419，主要针对表单元素是自定义组件的情况。
 * 例如自定义组件中也有表单，当表单验证失败时，这个子表单里面的验证元素也会全部失效。使用完后，重新启用验证。
 */
const fieldValidation = (fieldName: string) => {
  //移除表单项的校验结果。
  formRef.value!.fieldValidation(fieldName);
};
defineExpose({
  // 参考vue3官方文档，需要将函数暴露出去才能被父组件调用，setup模式是不暴露的
  openDialog,
  closeDialog,
  fieldValidation,
  clearFieldValidation
});
</script>

<style lang="scss" scoped></style>
